package com.sunway.lc.ftp.process;

import com.sunway.ftp.config.FTPClientConfig;
import com.sunway.ftp.config.FTPDownloadConfig;
import com.sunway.ftp.config.FTPUploadConfig;
import com.sunway.ftp.process.FtpProcess;
import com.sunway.ftp.properties.FTPSyncProperties;
import com.sunway.ftp.service.FtpSyncService;
import com.sunway.ftp.tags.FileTag;
import com.sunway.lc.ftp.redis.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * LC参数文件同步
 */
//@Component
@Slf4j
public class FtpSyncProcess {
    public static ConcurrentHashMap<String, FTPClient> clientConcurrentHashMap = new ConcurrentHashMap<>();
    public static final String REMOTE_KEY_PREFIX = "REMOTE_";
    public static final String LOCAL_KEY_PREFIX = "LOCAL_";
    public static final String SUCCESS_KEY_PREFIX = "SUCCESS_";             // 成功完成同步的文件缓存前缀  记录缓存,避免重复下载
    public static final long SUCCESS_DATA_KEEP_TIME = 7 * 24 * 3600;        // 缓存记录保存的时间，过期自动从缓存删除
    private FtpSyncService ftpSyncService;
    private FTPClientConfig ftpClientConfig;
    private FTPDownloadConfig ftpDownloadConfig;
    private FtpProcess ftpProcess;
    private RedisUtil redisUtil;
    private RedisTemplate redisTemplate;

    private FTPUploadConfig ftpUploadConfig;

    @Autowired
    public static void setClientConcurrentHashMap(ConcurrentHashMap<String, FTPClient> clientConcurrentHashMap) {
        FtpSyncProcess.clientConcurrentHashMap = clientConcurrentHashMap;
    }
    @Autowired
    public void setFtpSyncService(FtpSyncService ftpSyncService) {
        this.ftpSyncService = ftpSyncService;
    }
    @Autowired
    public void setFtpClientConfig(FTPClientConfig ftpClientConfig) {
        this.ftpClientConfig = ftpClientConfig;
    }
    @Autowired
    public void setFtpDownloadConfig(FTPDownloadConfig ftpDownloadConfig) {
        this.ftpDownloadConfig = ftpDownloadConfig;
    }
    @Autowired
    public void setFtpProcess(FtpProcess ftpProcess) {
        this.ftpProcess = ftpProcess;
    }
    @Autowired
    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }
    @Autowired
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    @Autowired
    public void setFtpUploadConfig(FTPUploadConfig ftpUploadConfig) {
        this.ftpUploadConfig = ftpUploadConfig;
    }

//    @Scheduled(fixedDelay = 1000 * 20)
    public void download(){
        List<FTPSyncProperties> downloadConfigItems = ftpDownloadConfig.getItems();
        try {
            configFtpClientCache(downloadConfigItems, clientConcurrentHashMap);
        } catch (IOException e) {
            log.error("连接FTP失败", e);
        }
        downloadConfigItems.stream().forEach((item)->{
            String name = item.getName();
            FTPClient ftpClient = clientConcurrentHashMap.get(name);
            // 扫描FTP远程目录信息
            if(ftpClient != null && ftpClient.isAvailable()){
                try {
                    // 远程目录文件信息
                    List<FileTag> remoteFileTags = ftpSyncService.scanFtpPath(ftpClient, item.getRemotePath());
                    Map<String, FileTag> remoteFileTagCache = ftpSyncService.generateFileTagMapInfo(remoteFileTags);

                    Map<String, FileTag> redisFileTags = redisTemplate.opsForHash().entries(REMOTE_KEY_PREFIX + name);

                    // 比较数据，确认哪些数据需要下载
                    Map<Integer, List<FileTag>> integerListMap = ftpSyncService.generateSyncType(redisFileTags, remoteFileTags);
                    List<FileTag> downloadList = integerListMap.get(FtpSyncService.SYNC_TYPE_ADD);
                    List<FileTag> updateList = integerListMap.get(FtpSyncService.SYNC_TYPE_UPDATE);
                    List<FileTag> deleteList = integerListMap.get(FtpSyncService.SYNC_TYPE_DELETE);

                    final FTPClient client = ftpClient;
                    // 更新本地目录文件
                    downloadList.stream().forEach((fileTag)->{
                        String info = String.format("从FTP下载文件【%s, %s】", fileTag.getRelativePath(), fileTag.getFilename());
                        log.info(info);
                        try {
                            downloadFromFtp(item, client, fileTag);
                            String key = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
                            updateRedisCache(REMOTE_KEY_PREFIX + name, key, fileTag);
                        } catch (IOException e) {
                            log.error("从FTP下载文件失败", e);
                        }
                    });

                    updateList.stream().forEach((fileTag -> {
                        String info = String.format("从FTP更新文件【%s, %s】", fileTag.getRelativePath(), fileTag.getFilename());
                        try {
                            log.info(info);
                            downloadFromFtp(item, client, fileTag);
                            String key = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
                            updateRedisCache(REMOTE_KEY_PREFIX + name, key, fileTag);
                        } catch (IOException e) {
                            log.error("从FTP下载文件失败", e);
                        }
                    }));

                    deleteList.stream().forEach((fileTag -> {
                        String key = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
                        deleteRedisCache(REMOTE_KEY_PREFIX + name, key);
                    }));

                    ftpSyncService.deleteFtpEmptyDirectory(item.getRemotePath(), client);
                    if(item.isDelete()){
                        log.info("清理本地文件目录中的冗余文件");
                        cleanLocalFiles(item.getLocalPath(), remoteFileTagCache.keySet());
                    }
                } catch (IOException e) {
                    log.error("扫描FTP失败", e);
                    clientConcurrentHashMap.remove(name);
                }
            }
            else{
                log.info("FTP连接当前不可用，连接可能断开或者正在进行FTP操作");
            }

        });
    }

//    @Scheduled(fixedDelay = 1000 * 20)
    public void upload(){
        List<FTPSyncProperties> ftpUploadConfigItems = ftpUploadConfig.getItems();
        try {
            configFtpClientCache(ftpUploadConfigItems, clientConcurrentHashMap);
        } catch (IOException e) {
            log.error("连接FTP失败", e);
        }
        ftpUploadConfigItems.stream().forEach((item) ->{
            String name = item.getName();
            FTPClient ftpClient = clientConcurrentHashMap.get(name);
            // 扫描本地目录信息
            if(ftpClient != null && ftpClient.isAvailable()){
                List<FileTag> localFileTags = ftpSyncService.scanLocalPath(item.getLocalPath(), FtpSyncService.FILE_PATH_SEPARATOR);
                Map<String, FileTag> localTagCache = ftpSyncService.generateFileTagMapInfo(localFileTags);
                Map<String, FileTag> redisFileTags = redisTemplate.opsForHash().entries(LOCAL_KEY_PREFIX + name);

                // 比较数据，确认哪些数据需要下载
                Map<Integer, List<FileTag>> integerListMap = ftpSyncService.generateSyncType(redisFileTags, localFileTags);
                List<FileTag> uploadList = integerListMap.get(FtpSyncService.SYNC_TYPE_ADD);
                List<FileTag> updateList = integerListMap.get(FtpSyncService.SYNC_TYPE_UPDATE);
                List<FileTag> deleteList = integerListMap.get(FtpSyncService.SYNC_TYPE_DELETE);

                final FTPClient client = ftpClient;
                // 更新FTP目录文件
                uploadList.stream().forEach((fileTag)->{
                    String info = String.format("上传文件到FTP【%s, %s】", fileTag.getRelativePath(), fileTag.getFilename());
                    log.info(info);
                    try {
                        uploadToFtp(item, client, fileTag);
                        String key = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
                        updateRedisCache(LOCAL_KEY_PREFIX + name, key, fileTag);
                    } catch (IOException e) {
                        log.error("上传文件到FTP失败", e);
                    }
                });

                updateList.stream().forEach((fileTag -> {
                    String info = String.format("更新FTP文件【%s, %s】", fileTag.getRelativePath(), fileTag.getFilename());
                    log.info(info);
                    try {
                        uploadToFtp(item, client, fileTag);
                        String key = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
                        updateRedisCache(LOCAL_KEY_PREFIX + name, key, fileTag);
                    } catch (IOException e) {
                        log.error("上传文件到FTP失败", e);
                    }
                }));

                deleteList.stream().forEach((fileTag -> {
                    String key = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
                    deleteRedisCache(LOCAL_KEY_PREFIX + name, key);
                }));

                if(item.isDelete()){
                    log.info("清理FTP目录中的冗余文件");
                    try {
                        cleanRemoteFiles(client, item.getRemotePath(), localTagCache.keySet());
                    } catch (IOException e) {
                        log.error("清理FTP目录中的冗余文件失败", e);
                    }
                }
            }
            else{
                log.info("FTP连接当前不可用，连接可能断开或者正在进行FTP操作");
                clientConcurrentHashMap.remove(name);
            }

        });
    }

    /**
     * FTPClient 缓存信息配置
     * @param ftpSyncPropertiesList      ftp配置信息列表
     * @param clientConcurrentHashMap    保存FTP连接的容器map
     */
    private void configFtpClientCache(List<FTPSyncProperties> ftpSyncPropertiesList, ConcurrentHashMap<String, FTPClient> clientConcurrentHashMap) throws IOException {
        ftpSyncPropertiesList.stream().forEach((item) -> {
            String name = item.getName();
            FTPClient ftpClient = clientConcurrentHashMap.get(name);
            if (ftpClient == null) {
                try {
                    ftpClient = ftpProcess.connect(item, ftpClientConfig);
                    clientConcurrentHashMap.put(name, ftpClient);
                } catch (IOException e) {
                    log.error("连接FTP失败", e);
                    return;
                }
            }
        });
    }

    /**
     * 更新缓存
     */
    private void updateRedisCache(String redisKey, String field, FileTag fileTag){
        redisTemplate.opsForHash().put(redisKey, field, fileTag);
    }

    /**
     * 删除redis中的map中的元素
     */
    private void deleteRedisCache(String redisKey, String field){
        redisTemplate.opsForHash().delete(redisKey, field);
    }

    /**
     * 清理本地文件目录的文件
     * 如果文件|目录在本地目录下存在，但是远程FTP不存在
     * 则删除本地文件
     * @param rootPath          本地文件根目录
     * @param remoteFileSet     远程文件信息
     */
    private void cleanLocalFiles(String rootPath, Set<String> remoteFileSet) {
        List<FileTag> localFileTags = ftpSyncService.scanLocalPath(rootPath, FtpSyncService.FILE_PATH_SEPARATOR);
        Map<String, FileTag> tagMap = ftpSyncService.generateFileTagMapInfo(localFileTags);
        Set<String> localFileSet = tagMap.keySet();
        Set<String> toDeleteSet = ftpSyncService.diff(localFileSet, remoteFileSet);
        toDeleteSet.stream().forEach((path) ->{
            File file = new File(rootPath + FtpSyncService.FILE_PATH_SEPARATOR + path);
            if(file.exists()){
                if((file.isDirectory() && file.listFiles().length == 0 ) || !file.isDirectory()){
                    // 只删除文件或者空目录
                    String info = String.format("删除本地文件：【%s】", file.getPath());
                    log.info(info);
                    file.delete();
                }
            }
        });
        ftpSyncService.deleteEmptyDirectory(rootPath);
    }

    /**
     * 清理FTP文件目录的文件
     * 如果文件|目录在FTP目录下存在，但是本地目录不存在
     * 则删除FTP文件文件
     * @param client                    FTP连接信息
     * @param remotePath                远程FTP指定目录
     * @param localFileSet              本地文件信息
     */
    private void cleanRemoteFiles(FTPClient client, String remotePath, Set<String> localFileSet) throws IOException {
        List<FileTag> fileTags = ftpSyncService.scanFtpPath(client, remotePath);
        Map<String, FileTag> tagMap = ftpSyncService.generateFileTagMapInfo(fileTags);
        Set<String> remoteFileSet = tagMap.keySet();
        Set<String> toDeleteSet = ftpSyncService.diff(remoteFileSet, localFileSet);
        toDeleteSet.stream().forEach((path) ->{
            try {
                int pos = path.lastIndexOf(FtpSyncService.FILE_PATH_SEPARATOR);
                if(pos != -1){
                    String filename = path.substring(pos + 1);
                    String dir = remotePath + path.substring(0, pos);
                    ftpProcess.deleteFile(client, dir, filename);
                }
            } catch (IOException e) {
                log.error("ftp删除文件失败");
            }
        });
        ftpSyncService.deleteFtpEmptyDirectory(remotePath, client);
    }

    /**
     * 从FTP下载文件
     * @param item              ftp同步配置信息
     * @param client            FTP连接信息
     * @param fileTag           需要下载的文件描述信息
     */
    private void downloadFromFtp(FTPSyncProperties item, FTPClient client, FileTag fileTag) throws IOException {
        if(fileTag.isDir()){
            // 如果是FTP目录，则本地直接创建同名文件目录即可
            String dir = fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
            mkdir(item.getLocalPath(), dir);
        }
        else{
            // 如果是FTP文件，则需要下载到本地
            // 下载之前，先创建目录
            String dir = fileTag.getRelativePath();
            mkdir(item.getLocalPath(), dir);
            String localFileName = item.getLocalPath() + FtpSyncService.FILE_PATH_SEPARATOR
                    + fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR
                    + fileTag.getFilename();
            String remoteFullPath = item.getRemotePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getRelativePath();
            ftpProcess.downloadFile(client, remoteFullPath, fileTag.getFilename(), localFileName);
            if(item.isDeleteAfterTransfer()){
                client.deleteFile(remoteFullPath + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename());
            }
        }
    }

    /**
     * 上传文件到FTP目录
     * @param item              ftp同步配置信息
     * @param client            FTP连接信息
     * @param fileTag           需要下载的文件描述信息
     */
    private void uploadToFtp(FTPSyncProperties item, FTPClient client, FileTag fileTag) throws IOException {
        if(fileTag.isDir()){
            // 如果是目录，则FTP上直接创建该目录
            String dir = item.getRemotePath() + fileTag.getRelativePath() + FtpSyncService.FILE_PATH_SEPARATOR + fileTag.getFilename();
            ftpProcess.mkdir(client, dir);
        }
        else{
            ftpProcess.uploadFile(client, item.getRemotePath(), fileTag.getRelativePath(), fileTag.getFilename(), item.getLocalPath());
        }
    }

    /**
     * 创建本地目录
     * @param rootPath          本地根目录
     * @param relativePath      本地相对目录
     */
    private void mkdir(String rootPath, String relativePath){
        String fullPath = rootPath + FtpSyncService.FILE_PATH_SEPARATOR + relativePath;
        fullPath = ftpSyncService.formatFilePath(fullPath);
        File dirs = new File(fullPath);
        if(!dirs.exists()){
            dirs.mkdirs();
        }
    }

}
